game_manager_lib\services\recommendation/
reports.rs

1//! Exportação de Relatórios de Análise de Recomendações
2//!
3//! Este módulo contém funções para exportar relatórios de análise em diferentes formatos
4//! (JSON, TXT, CSV). Separado do módulo de análise para manter responsabilidades distintas.
5
6use super::analysis::{DetailedScoreBreakdown, RecommendationAnalysisReport};
7use std::fs::File;
8use std::io::Write;
9
10// === EXPORTAÇÃO DE RELATÓRIOS ===
11
12/// Exporta relatório em formato JSON
13pub fn export_report_json(
14    report: &RecommendationAnalysisReport,
15    filename: &str,
16) -> std::io::Result<()> {
17    let json = serde_json::to_string_pretty(report)?;
18    let mut file = File::create(filename)?;
19    file.write_all(json.as_bytes())?;
20    Ok(())
21}
22
23/// Exporta relatório em formato TXT legível
24pub fn export_report_txt(
25    report: &RecommendationAnalysisReport,
26    filename: &str,
27) -> std::io::Result<()> {
28    let mut file = File::create(filename)?;
29
30    write_header(&mut file)?;
31    write_metadata(&mut file, report)?;
32    write_configuration(&mut file, report)?;
33    write_statistics(&mut file, report)?;
34    write_user_profile(&mut file, report)?;
35    write_tag_influence(&mut file, report)?;
36    write_genre_influence(&mut file, report)?;
37    write_reason_distribution(&mut file, report)?;
38    write_top_games(&mut file, report)?;
39
40    Ok(())
41}
42
43/// Exporta apenas os jogos em formato CSV
44pub fn export_games_csv(games: &[DetailedScoreBreakdown], filename: &str) -> std::io::Result<()> {
45    let mut file = File::create(filename)?;
46
47    // Header
48    writeln!(
49        file,
50        "Rank,Title,Final Score,Affinity,Context,Diversity,Genre,Tag,Series,CB Total,CF,Age Penalty,Reason"
51    )?;
52
53    // Dados
54    for game in games {
55        writeln!(
56            file,
57            "{},{},{:.4},{:.2},{:.2},{:.2},{:.2},{:.2},{:.2},{:.2},{:.2},{:.4},{}",
58            game.final_rank,
59            game.game_title.replace(",", ";"),
60            game.final_score,
61            game.affinity_score,
62            game.context_score,
63            game.diversity_score,
64            game.genre_score,
65            game.tag_score,
66            game.series_score,
67            game.total_cb,
68            game.total_cf,
69            game.age_penalty,
70            game.reason_label.replace(",", ";")
71        )?;
72    }
73
74    Ok(())
75}
76
77// === FUNÇÕES AUXILIARES DE ESCRITA ===
78
79fn write_header(file: &mut File) -> std::io::Result<()> {
80    writeln!(
81        file,
82        "================================================================================"
83    )?;
84    writeln!(
85        file,
86        "RELATÓRIO DE ANÁLISE DE RECOMENDAÇÕES - PLAYLITE v4.0"
87    )?;
88    writeln!(
89        file,
90        "================================================================================"
91    )?;
92    writeln!(file)?;
93    Ok(())
94}
95
96fn write_metadata(file: &mut File, report: &RecommendationAnalysisReport) -> std::io::Result<()> {
97    writeln!(file, "Timestamp: {}", report.timestamp)?;
98    writeln!(file, "Total de jogos analisados: {}", report.total_games)?;
99    writeln!(file)?;
100    Ok(())
101}
102
103fn write_configuration(
104    file: &mut File,
105    report: &RecommendationAnalysisReport,
106) -> std::io::Result<()> {
107    writeln!(file, "📊 CONFIGURAÇÕES")?;
108    writeln!(
109        file,
110        "--------------------------------------------------------------------------------"
111    )?;
112    writeln!(file, "Content Weight: {:.2}", report.config.content_weight)?;
113    writeln!(
114        file,
115        "Collaborative Weight: {:.2}",
116        report.config.collaborative_weight
117    )?;
118    writeln!(file, "Age Decay: {:.2}", report.config.age_decay)?;
119    writeln!(file, "Favor Series: {}", report.config.favor_series)?;
120    writeln!(
121        file,
122        "Filter Adult Content: {}",
123        report.user_settings.filter_adult_content
124    )?;
125    writeln!(file, "Series Limit: {}", report.user_settings.series_limit)?;
126    writeln!(file)?;
127    Ok(())
128}
129
130fn write_statistics(file: &mut File, report: &RecommendationAnalysisReport) -> std::io::Result<()> {
131    writeln!(file, "📈 ESTATÍSTICAS GERAIS")?;
132    writeln!(
133        file,
134        "--------------------------------------------------------------------------------"
135    )?;
136    writeln!(
137        file,
138        "Score Final Médio: {:.4}",
139        report.stats.avg_final_score
140    )?;
141    writeln!(
142        file,
143        "Score Final Mediana: {:.4}",
144        report.stats.median_final_score
145    )?;
146    writeln!(
147        file,
148        "Score Final Máximo: {:.4}",
149        report.stats.max_final_score
150    )?;
151    writeln!(
152        file,
153        "Score Final Mínimo: {:.4}",
154        report.stats.min_final_score
155    )?;
156    writeln!(file)?;
157    writeln!(
158        file,
159        "CB Médio: {:.2}  |  CF Médio: {:.2}",
160        report.stats.avg_cb_score, report.stats.avg_cf_score
161    )?;
162    writeln!(file)?;
163    writeln!(
164        file,
165        "Affinity Médio: {:.2}  ({:.1}%)",
166        report.stats.avg_affinity_score, report.stats.affinity_proportion
167    )?;
168    writeln!(
169        file,
170        "Context Médio: {:.2}  ({:.1}%)",
171        report.stats.avg_context_score, report.stats.context_proportion
172    )?;
173    writeln!(
174        file,
175        "Diversity Médio: {:.2}  ({:.1}%)",
176        report.stats.avg_diversity_score, report.stats.diversity_proportion
177    )?;
178    writeln!(file)?;
179    writeln!(
180        file,
181        "Genre Score Médio: {:.2}  ({:.1}%)",
182        report.stats.avg_genre_score, report.stats.genre_proportion
183    )?;
184    writeln!(file, "Tag Score Médio: {:.2}", report.stats.avg_tag_score)?;
185    writeln!(
186        file,
187        "Series Score Médio: {:.2}",
188        report.stats.avg_series_score
189    )?;
190    writeln!(
191        file,
192        "Age Penalty Médio: {:.4}",
193        report.stats.avg_age_penalty
194    )?;
195    writeln!(file)?;
196    Ok(())
197}
198
199fn write_user_profile(
200    file: &mut File,
201    report: &RecommendationAnalysisReport,
202) -> std::io::Result<()> {
203    writeln!(file, "👤 PERFIL DO USUÁRIO")?;
204    writeln!(
205        file,
206        "--------------------------------------------------------------------------------"
207    )?;
208    writeln!(
209        file,
210        "Total de gêneros no perfil: {}",
211        report.profile_stats.total_genres
212    )?;
213    writeln!(
214        file,
215        "Total de tags no perfil: {}",
216        report.profile_stats.total_tags
217    )?;
218    writeln!(
219        file,
220        "Total de séries no perfil: {}",
221        report.profile_stats.total_series
222    )?;
223    writeln!(file)?;
224
225    if !report.profile_stats.top_genres.is_empty() {
226        writeln!(file, "Top 10 Gêneros no Perfil:")?;
227        for (idx, (genre, weight)) in report.profile_stats.top_genres.iter().enumerate() {
228            writeln!(file, "  {}. {} - peso: {:.2}", idx + 1, genre, weight)?;
229        }
230        writeln!(file)?;
231    }
232
233    if !report.profile_stats.top_tags.is_empty() {
234        writeln!(file, "Top 20 Tags no Perfil:")?;
235        for (idx, (slug, key, weight)) in report.profile_stats.top_tags.iter().enumerate() {
236            writeln!(
237                file,
238                "  {}. {} ({}) - peso: {:.2}",
239                idx + 1,
240                slug,
241                key,
242                weight
243            )?;
244        }
245        writeln!(file)?;
246    }
247    Ok(())
248}
249
250fn write_tag_influence(
251    file: &mut File,
252    report: &RecommendationAnalysisReport,
253) -> std::io::Result<()> {
254    writeln!(file, "🏷️  TOP TAGS POR INFLUÊNCIA")?;
255    writeln!(
256        file,
257        "--------------------------------------------------------------------------------"
258    )?;
259    for (idx, (_, tag)) in report.tag_influence.iter().take(20).enumerate() {
260        writeln!(file, "{}. {} ({})", idx + 1, tag.tag_name, tag.category)?;
261        writeln!(
262            file,
263            "   Jogos: {}  |  Avg: {:.2}  |  Max: {:.2}  |  Razão principal: {} vezes",
264            tag.games_count, tag.avg_contribution, tag.max_contribution, tag.times_as_reason
265        )?;
266    }
267    writeln!(file)?;
268    Ok(())
269}
270
271fn write_genre_influence(
272    file: &mut File,
273    report: &RecommendationAnalysisReport,
274) -> std::io::Result<()> {
275    writeln!(file, "🎮 TOP GÊNEROS POR INFLUÊNCIA")?;
276    writeln!(
277        file,
278        "--------------------------------------------------------------------------------"
279    )?;
280    for (idx, (name, genre)) in report.genre_influence.iter().take(10).enumerate() {
281        writeln!(file, "{}. {}", idx + 1, name)?;
282        writeln!(
283            file,
284            "   Jogos: {}  |  Avg: {:.2}  |  Max: {:.2}  |  Razão principal: {} vezes",
285            genre.games_count,
286            genre.avg_contribution,
287            genre.max_contribution,
288            genre.times_as_reason
289        )?;
290    }
291    writeln!(file)?;
292    Ok(())
293}
294
295fn write_reason_distribution(
296    file: &mut File,
297    report: &RecommendationAnalysisReport,
298) -> std::io::Result<()> {
299    writeln!(file, "📊 DISTRIBUIÇÃO DE RAZÕES")?;
300    writeln!(
301        file,
302        "--------------------------------------------------------------------------------"
303    )?;
304    let mut reasons: Vec<_> = report.reason_distribution.iter().collect();
305    reasons.sort_by(|a, b| b.1.cmp(a.1));
306    for (reason, count) in reasons {
307        let percentage = (*count as f32 / report.total_games as f32) * 100.0;
308        writeln!(file, "{}: {} ({:.1}%)", reason, count, percentage)?;
309    }
310    writeln!(file)?;
311    Ok(())
312}
313
314fn write_top_games(file: &mut File, report: &RecommendationAnalysisReport) -> std::io::Result<()> {
315    writeln!(file, "🎯 TOP 50 JOGOS RECOMENDADOS")?;
316    writeln!(
317        file,
318        "================================================================================"
319    )?;
320    for game in report.games.iter().take(50) {
321        writeln!(file)?;
322        writeln!(file, "#{} - {}", game.final_rank, game.game_title)?;
323        writeln!(
324            file,
325            "   Score Final: {:.4}  |  CB: {:.2}  |  CF: {:.2}",
326            game.final_score, game.total_cb, game.total_cf
327        )?;
328        writeln!(
329            file,
330            "   Affinity: {:.2}  |  Context: {:.2}  |  Diversity: {:.2}",
331            game.affinity_score, game.context_score, game.diversity_score
332        )?;
333        writeln!(
334            file,
335            "   Genre: {:.2}  |  Tag: {:.2}  |  Series: {:.2}  |  Age Penalty: {:.4}",
336            game.genre_score, game.tag_score, game.series_score, game.age_penalty
337        )?;
338        writeln!(file, "   Razão: {}", game.reason_label)?;
339
340        if !game.top_genres.is_empty() {
341            write!(file, "   Top Gêneros: ")?;
342            for (i, (genre, score)) in game.top_genres.iter().enumerate() {
343                if i > 0 {
344                    write!(file, ", ")?;
345                }
346                write!(file, "{} ({:.1})", genre, score)?;
347            }
348            writeln!(file)?;
349        }
350
351        if !game.top_affinity_tags.is_empty() {
352            write!(file, "   Top Tags (Affinity): ")?;
353            for (i, (tag, score)) in game.top_affinity_tags.iter().take(5).enumerate() {
354                if i > 0 {
355                    write!(file, ", ")?;
356                }
357                write!(file, "{} ({:.1})", tag, score)?;
358            }
359            writeln!(file)?;
360        }
361        writeln!(file)?;
362    }
363
364    Ok(())
365}